<?php

namespace app\common\library;

use Lcobucci\Clock\SystemClock;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Key\InMemory;
use DateTimeImmutable, InvalidArgumentException;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Validation\Constraint\{
    IssuedBy, SignedWith, ValidAt
};
use Lcobucci\JWT\Token;

/**
 * @link https://lcobucci-jwt.readthedocs.io/en/4.0.0/upgrading/#v3x-to-v4x
 * Class Jwt
 * @package app\common\library
 */
class Jwt
{
    private Configuration $config;
    private Token $token;
    private string $iss = 'thinkphp.study.com';

    private static ?object $instance = null;

    private function __construct(Configuration $config)
    {
        $this->config = $config;
        $this->config->setValidationConstraints(
            new IssuedBy($this->iss),
            new ValidAt(SystemClock::fromSystemTimezone()),
            new SignedWith($config->signer(), $config->signingKey()),
        );
    }

    private function __clone()
    {
    }

    /**
     * @return Jwt
     */
    public static function instance(): Jwt
    {
        if (!self::$instance instanceof Jwt) {
            self::$instance = new Jwt(
                Configuration::forSymmetricSigner(
                    new Sha256(),
                    InMemory::plainText('testing')
                )
            );
        }
        return self::$instance;
    }

    /**
     * 创建令牌
     * @param string $claimData 用户或者其他非敏感信息
     * @return string
     */
    public function createJwtToken(string $claimData): string
    {
        $dateTime = new DateTimeImmutable();
        $token = $this->config->builder()
            // 签发者
            ->issuedBy($this->iss)
            // 签名算法
            ->withHeader('alg', 'HS256')
            // 接收方
            ->permittedFor('request_user')
            // 令牌唯一标识
            ->identifiedBy(md5(uniqid('', true)))
            // 面向用户
            ->relatedTo('request_user_success')
            // 令牌创建时间
            ->issuedAt($dateTime)
            // 当前时间之前该令牌不被接受
            ->canOnlyBeUsedAfter($dateTime->modify('-1 minute'))
            // 当前时间令牌生命周期
            ->expiresAt($dateTime->modify('+10000 hour'))
            // 非敏感信息
            ->withClaim('claim', $claimData)
            ->getToken($this->config->signer(), $this->config->signingKey());

        return $token->toString();
    }

    /**
     * 认证令牌
     * @param string $jwt
     */
    public function authenticateJwtToken(string $jwt): void
    {
        $this->token = $this->config->parser()->parse($jwt);
        $constraints = $this->config->validationConstraints();

        if (!$this->config->validator()->validate($this->token, ...$constraints)) {
            throw new InvalidArgumentException('Invalid token provided');
        }
    }

    /**
     * 获取非敏感信息
     * @return array
     */
    public function getJwtClaimData(): array
    {
        $claim = $this->token->claims()->get('claim');
        return json_decode($claim, true);
    }
}